/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.openide.text; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Color; import java.awt.Font; import java.awt.Dialog; import java.awt.Dimension; import java.awt.RenderingHints; import java.awt.print.Printable; import java.awt.print.PageFormat; import java.awt.print.PrinterGraphics; import java.awt.print.PrinterException; import java.awt.print.PrinterAbortException; import java.awt.print.PrinterJob; import java.awt.print.Paper; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.font.LineBreakMeasurer; import java.awt.font.TextLayout; import java.awt.font.FontRenderContext; import java.text.AttributedCharacterIterator; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Iterator; import java.util.Date; import javax.swing.text.Document; import javax.swing.text.BadLocationException; import org.openide.TopManager; import org.openide.DialogDescriptor; import org.openide.NotifyDescriptor; import org.openide.util.HelpCtx; /** The class creates from an instance of AttributedCharacterIterator * a java.awt.print.Pageable object. * * @author Ales Novak */ final class DefaultPrintable extends Object implements Printable { /** * for each item is created new LineBreakMeasurer */ private AttributedCharacterIterator[] styledTexts; /** expected page */ private int pageno; /** created text layouts * @associates TextLayout*/ private ArrayList textLayouts; /** page indices to textLayouts list */ private int[] pageIndices; /** pageIndices size */ private int pageIndicesSize; /** iterator over textLayouts */ private int currentLayout; /** curent styledText entry */ private int currentStyledText; /** current LineBreakMeasurer */ private LineBreakMeasurer lineBreakMeasurer; /** maximal page */ private int maxPage; // not used now /** current line */ private int currentLine; /** text layouts that starts new line (those that were not created thanks to wrapping) * @associates TextLayout*/ private ArrayList startLayouts; /** page to line indexes (page 5 starts with line 112) */ private int[] lineIndices; // pageIndicesSize /** Number of args. */ private static final int ARG_SIZE = 3; /** Arguments for page. */ private Object[] pageArgs; /** Header of each page. */ private MessageFormat header; /** Should be header printed? */ private boolean printHeader; /** Bottom of each page. */ private MessageFormat footer; /** Should be footer printed? */ private boolean printFooter; /** Options */ private static PrintSettings printSettings; /** CancellationPanel */ private CancellationPanel cancellationPanel; /** Dialog */ private Dialog cancellationDialog; /** * @param attrs an AttributedCharacterIterator * @param filename */ private DefaultPrintable(AttributedCharacterIterator[] iter, String filename) { if ((iter == null) || (iter.length == 0)) { throw new IllegalArgumentException(); } if (printSettings == null) { printSettings = (PrintSettings) PrintSettings.findObject(PrintSettings.class, true); } // bugfix for sun.awt.Bidi line 250 replaceEmptyIterators(iter); styledTexts = iter; pageno = 0; textLayouts = new ArrayList(100); // 100 lines pageIndices = new int[50]; pageIndicesSize = 0; currentLayout = 0; currentStyledText = 0; lineBreakMeasurer = null; maxPage = Integer.MAX_VALUE; // not used currentLine = 0; startLayouts = new ArrayList(10); // 10 lines lineIndices = new int[pageIndices.length]; pageArgs = new Object[ARG_SIZE]; pageArgs[2] = filename; pageArgs[1] = new Date(System.currentTimeMillis()); header = new MessageFormat(getHeaderFormat()); printHeader = !getHeaderFormat().equals(""); // NOI18N footer = new MessageFormat(getFooterFormat()); printFooter = !getFooterFormat().equals(""); // NOI18N } /** * @param doc printed document */ public DefaultPrintable(Document doc) { this(getIterators(doc), getFilename(doc)); } /** * Prints a page. * * @param g a Graphics * @param pf a PageFormat * @param pageNo Which page? */ public int print(Graphics g, PageFormat pf, int pageNo) throws PrinterException { if (pageNo > maxPage) { closeDialog(); return Printable.NO_SUCH_PAGE; } else if (pageno + 1 >= pageNo) { pageno = pageNo; //same or advance it } else { // skipped? closeDialog(); throw new IllegalStateException(); } // stop if cancelled if ((g instanceof PrinterGraphics) && isCancelled(((PrinterGraphics) g).getPrinterJob())) { closeDialog(); throw new PrinterAbortException(); } if ((cancellationPanel == null) && (g instanceof PrinterGraphics)) { PrinterJob pJob = ((PrinterGraphics) g).getPrinterJob(); createCancellationPanel(pJob); } if (cancellationPanel != null) { cancellationPanel.setPageno(pageNo); packDialog(); } // line numbers init int startLine = 0; int correction = 3; // magic - take pencil, paper, and start measuring. String lineNo = null; if (lineNumbers()) { // not used /* lineNo = Integer.toString(startLine = page2Line(pageNo)); // correction may not be ok if (startLine < 100) { lineNo = lineNo + " "; } else { lineNo = lineNo + " "; } correction = g.getFontMetrics().stringWidth(lineNo); */ } g.setColor(Color.black); final Graphics2D graphics = (Graphics2D) g; final Point2D.Float pen = new Point2D.Float(getImageableXPatch(pf), getImageableYPatch(pf) ); /* System.out.println("PEN IS: " + pen); pen = new Point2D.Float((float) pf.getImageableWidth(), (float) pf.getImageableHeight() ); System.out.println("END IS: " + pen); Paper paper = pf.getPaper(); System.out.println("DEF IS: " + paper.getHeight() + ", " + paper.getWidth()); */ // header & footer init pageArgs[0] = new Integer(pageNo + 1); // pages numbered from 1 TextLayout headerString = new TextLayout(header.format(pageArgs), getHeaderFont(), graphics.getFontRenderContext()); TextLayout footerString = new TextLayout(footer.format(pageArgs), getFooterFont(), graphics.getFontRenderContext()); float pageBreakCorrection = (printHeader ? headerString.getAscent() + (headerString.getDescent() + headerString.getLeading()) * 2 : 0) + (printFooter ? footerString.getAscent() * 2 + footerString.getDescent() + footerString.getLeading() : 0); // for now suppose that getImageableWidthPatch(pf) is always // the same during the same print job final float wrappingWidth = (wrap() ? (float) pf.getImageableWidth() - correction : Float.MAX_VALUE); final float pageBreak = ((float) pf.getImageableHeight()) - pageBreakCorrection; final FontRenderContext frCtx = graphics.getFontRenderContext(); boolean pageExists = false; // page rendering for (TextLayout layout = layoutForPage(pageNo, wrappingWidth, frCtx); (pen.y < pageBreak); layout = nextLayout(wrappingWidth, frCtx) ) { if (layout == null) { maxPage = pageNo; break; } if (!pageExists) { // draw header if (printHeader) { pen.y += headerString.getAscent(); float center = computeStart(headerString.getBounds(), (float) pf.getImageableWidth(), getHeaderAlignment()); float dx = (headerString.isLeftToRight() ? center: wrappingWidth - headerString.getAdvance() - center); headerString.draw(graphics, pen.x + dx, pen.y); pen.y += (headerString.getDescent() + headerString.getLeading()) * 2; } pageExists = true; } pen.y += layout.getAscent() * getLineAscentCorrection(); // line number handling if (lineNumbers() && isNewline(layout, startLine)) { /* lineNo = Integer.toString(++startLine); // + 1 -> lines starts from 1 if (startLine < 100) { lineNo = lineNo + " "; } else { lineNo = lineNo + " "; } // graphics.drawString(lineNo, (int) pen.x, (int) pen.y); TextLayout tl = new TextLayout(lineNo, lineNumbersFont(), graphics.getFontRenderContext()); tl.draw(graphics, pen.x, pen.y); */ } float dx = (layout.isLeftToRight() ? 0 : wrappingWidth - layout.getAdvance()); layout.draw(graphics, correction + pen.x + dx, pen.y); pen.y += (layout.getDescent() + layout.getLeading()) * getLineAscentCorrection(); } // draw footer if (printFooter && pageExists) { pen.y = pageBreak; pen.y += footerString.getAscent() * 2; float center = computeStart(footerString.getBounds(), (float) pf.getImageableWidth(), getFooterAlignment()); float dx = (footerString.isLeftToRight() ? 0 + center : wrappingWidth - footerString.getAdvance() - center); footerString.draw(graphics, pen.x + dx, pen.y); } // stop if cancelled if ((g instanceof PrinterGraphics) && isCancelled(((PrinterGraphics) g).getPrinterJob())) { closeDialog(); throw new PrinterAbortException(); } // at least one layout draw? if (!pageExists) { closeDialog(); return Printable.NO_SUCH_PAGE; } else { return Printable.PAGE_EXISTS; } } /* for following two methods: * windows page setup dialog behaves incorrectly for LANDSCAPE format * the x coordinate is influenced by RIGHT margin instead of LEFT margin * the y coordinate ... BOTTOM instead of TOP * #3732 */ /** Patch for a bug in the PageFormat class * @param pf PageFormat * @return imageable x coordination for this page format */ private float getImageableXPatch(PageFormat pf) { if (pf.getOrientation() == PageFormat.LANDSCAPE) { double ret = pf.getPaper().getHeight() - (pf.getImageableX() + pf.getImageableWidth()); return (float) Math.round(ret); } else { return (float) pf.getImageableX(); } } /** Patch for a bug in the PageFormat class * @param pf PageFormat * @return imageable y coordination for this page format */ private float getImageableYPatch(PageFormat pf) { if (pf.getOrientation() == PageFormat.LANDSCAPE) { double ret = pf.getPaper().getWidth() - (pf.getImageableY() + pf.getImageableHeight()); return (float) Math.round(ret); } else { return (float) pf.getImageableY(); } } /** Translates given page number to line number. * @param pageNo * @return number of first line on the page */ private int page2Line(int pageNo) { if (pageNo == 0) { return 0; } else { return (pageNo == pageIndicesSize ? Math.max(startLayouts.size() - 1, 0) : lineIndices[pageNo]); } } /** * @param tl a TextLayout * @param currentLine * @return <tt>true</tt> iff <tt>tl</tt> is a TextLayout that does not represent wrapped line */ private boolean isNewline(TextLayout tl, int currentLine) { if (currentLine >= startLayouts.size()) { return false; // wrapping appeared } else { return startLayouts.get(currentLine) == tl; } } /** Computes alignment for a TextLayout with given bounds on the page with given width * and for given alignment policy. * * @param rect Bounds of a TextLayout * @param width page width * @param alignment one of @see PageSettings#LEFT @see PageSettings#CENTER @see PageSettings#RIGHT */ private static float computeStart(Rectangle2D rect, float width, int alignment) { float x; if (rect instanceof Rectangle2D.Float) { x = ((Rectangle2D.Float) rect).width; } else { x = (float) ((Rectangle2D.Double) rect).width; } if (x >= width) { return 0; } switch (alignment) { case PrintSettings.LEFT : return 0; case PrintSettings.RIGHT : return (width - x); default: return (width - x) / 2; } } /** * @param wrappingWidth width of the layout * @param frCtx for possible new instance of LineBreakMeasurer * @return next TextLayout that is to be rendered */ private TextLayout nextLayout(float wrappingWidth, FontRenderContext frc) { TextLayout l; if (currentLayout == textLayouts.size()) { LineBreakMeasurer old = lineBreakMeasurer; LineBreakMeasurer measurer = getMeasurer(frc); if (measurer == null) { return null; } l = measurer.nextLayout(wrappingWidth); textLayouts.add(l); if (old != measurer) { // new line startLayouts.add(l); } } else { l = (TextLayout) textLayouts.get(currentLayout); } currentLayout++; // advance to next return l; } /** Sets @see #currentLayout variable then calls nextLayout. * @param pageNo searched page * @param wrappingWidth width of the layout * @param frCtx for possible new instance of LineBreakMeasurer * @return next TextLayout that is to be rendered */ private TextLayout layoutForPage(int pageNo, float wrappingWidth, FontRenderContext frc) { if (pageNo > pageIndicesSize + 1) { throw new IllegalArgumentException(); } // first request for a page // pageNo==3 -> fourth page to print if (pageNo == pageIndicesSize) { // small array? if (pageIndicesSize >= pageIndices.length) { pageIndices = increaseArray(pageIndices); lineIndices = increaseArray(lineIndices); } // layouts for given page starts at: pageIndices[pageIndicesSize] = Math.max(textLayouts.size() - 1, 0); // remember - in the for loop above last layout is not printed // if page breaks lineIndices[pageIndicesSize++] = Math.max(startLayouts.size() - 1, 0); } currentLayout = pageIndices[pageNo]; // set the iterator return nextLayout(wrappingWidth, frc); // iterate } /** Called only if new TextLayouts are in need. * @param frc is used for possible new LineBreakMeasurer instance * @return current LineBreakMeasurer or <tt>null</tt> if no is available. */ private LineBreakMeasurer getMeasurer(FontRenderContext frc) { if (lineBreakMeasurer == null) { // first page to print lineBreakMeasurer = new LineBreakMeasurer(styledTexts[currentStyledText], frc); // no layouts available in this measurer? } else if (lineBreakMeasurer.getPosition() >= styledTexts[currentStyledText].getEndIndex()) { // next measurer is not available? if (currentStyledText == styledTexts.length - 1) { return null; // everything is printed } else { // use next styledTexts entry lineBreakMeasurer = new LineBreakMeasurer(styledTexts[++currentStyledText], frc); } } return lineBreakMeasurer; } // ------------------ options ----------------- /** @return true iff wrapping is on*/ private static boolean wrap() { return printSettings.getWrap(); } /** @return String describing header */ private static String getHeaderFormat() { return printSettings.getHeaderFormat(); } /** @return String describing footer */ private static String getFooterFormat() { return printSettings.getFooterFormat(); } /** @return font for header */ private static Font getHeaderFont() { return printSettings.getHeaderFont(); } /** @return font for footer */ private static Font getFooterFont() { return printSettings.getFooterFont(); } /** @return an alignment constant for footer */ private static int getFooterAlignment() { return printSettings.getFooterAlignment(); } /** @return an alignment constant for header */ private static int getHeaderAlignment() { return printSettings.getHeaderAlignment(); } /** @return a line ascent correction */ private static float getLineAscentCorrection() { return printSettings.getLineAscentCorrection(); } /** @return false */ private static boolean lineNumbers() { return false; } // not used private static Font lineNumbersFont() { return new Font("Courier", java.awt.Font.PLAIN, 6); // NOI18N } // ----------------- options end -------------- /** Creates new AttributedCharacterIterator for plain text. * * @return an AttributedCharacterIterator */ private static AttributedCharacterIterator[] getIterators(Document doc) { if (doc instanceof NbDocument.Printable) { return ((NbDocument.Printable) doc).createPrintIterators(); } // load options java.awt.Font f = new java.awt.Font("Courier", java.awt.Font.PLAIN, 8); // NOI18N AttributedCharacters achs = null; char[] chars = null; ArrayList iterators = new ArrayList(300); try { String document = doc.getText(0, doc.getLength()); // now chars are filled from the document int firstCharInDoc = 0; for (int i = 0; i < document.length(); i++) { // search for new lines if (document.charAt(i) == '\n') { chars = new char[i - firstCharInDoc + 1]; document.getChars(firstCharInDoc, chars.length + firstCharInDoc, chars, 0); achs = new AttributedCharacters(); achs.append(chars, f, Color.black); iterators.add(achs.iterator()); // new iterator for new line firstCharInDoc = i + 1; } } } catch (BadLocationException e){ TopManager.getDefault().notifyException(e); } AttributedCharacterIterator[] iters = new AttributedCharacterIterator[iterators.size()]; iterators.toArray(iters); return iters; } /** * @param doc * @return filename from which the document is loaded. */ private static String getFilename(Document doc) { String ret = (String) doc.getProperty(javax.swing.text.Document.TitleProperty); return (ret == null ? "UNKNOWN" : ret); // NOI18N } /** Doubles given array. The old one is then copied into the new one. * @return new int array */ private static int[] increaseArray(int[] old) { int[] ret = new int[2 * old.length]; System.arraycopy(old, 0, ret, 0, old.length); return ret; } // ------------------ cancellation dialog --------------------------- /** Creates cancellation dialog. * @param job PrinterJob */ private void createCancellationPanel(final PrinterJob job) { cancellationPanel= new CancellationPanel(job); DialogDescriptor ddesc = new DialogDescriptor( cancellationPanel, PrintSettings.getString("CTL_Print_cancellation"), false, new Object[] {PrintSettings.getString("CTL_Cancel")}, PrintSettings.getString("CTL_Cancel"), DialogDescriptor.BOTTOM_ALIGN, new HelpCtx (DefaultPrintable.class.getName () + ".cancelDialog"), // NOI18N new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent ev) { setCancelled(job); closeDialog(); } } ); setDialog(TopManager.getDefault().createDialog(ddesc)); } /** Closes cancellationDialog */ void closeDialog() { if (cancellationDialog != null) { cancellationDialog.setVisible(false); cancellationDialog.dispose(); } } /** @param <tt>d</tt> New value of <tt>cancellationDialog</tt>*/ void setDialog(Dialog d) { d.show(); d.pack(); cancellationDialog = d; } /** packs the dialog */ void packDialog() { if (cancellationDialog != null) { cancellationDialog.pack(); } } /** Marks this job as cancelled. * @param <tt>job</tt> */ void setCancelled(PrinterJob job) { job.cancel(); } /** @return <tt>true</tt> iff the job was cancelled */ boolean isCancelled(PrinterJob job) { return job.isCancelled(); } /** Replaces an empty iterator by an iterator conatining one space. */ private static void replaceEmptyIterators(AttributedCharacterIterator[] iters) { for (int i = 0; i < iters.length; i++) { AttributedCharacterIterator achit = iters[i]; if (achit.getBeginIndex() == achit.getEndIndex()) { AttributedCharacters at = new AttributedCharacters(); at.append(' ', getFontInstance(), Color.white); iters[i] = at.iterator(); } } } /** CancellationPanel class allows user to cancel current PrinterJob */ static final class CancellationPanel extends javax.swing.JPanel { /** Controlled PrinterJob */ private final PrinterJob job; /** Label indicating progress. */ private final javax.swing.JLabel printProgress; /** Format of displayed text. */ private final MessageFormat format; /** Parameters for <tt>format</tt>. */ private final Object[] msgParams; static final long serialVersionUID =-6419253408585188541L; /** * @param <tt>job</tt> PrinterJob * @exception IllegalArgumentException is thrown if <tt>job</tt> is <tt>null</tt>. */ public CancellationPanel(PrinterJob job) { if (job == null) { throw new IllegalArgumentException(); } this.job = job; format = new MessageFormat(PrintSettings.getString("CTL_Print_progress")); msgParams = new Object[1]; setLayout(new java.awt.GridBagLayout()); printProgress = new javax.swing.JLabel(); printProgress.setFont(getFontInstance()); java.awt.GridBagConstraints gridBagConstraints = new java.awt.GridBagConstraints(); add(printProgress, gridBagConstraints); } /** Advances progress. * @param <tt>pageno</tt> Page number that was printed. */ public void setPageno(int pageno) { msgParams[0] = new Integer(pageno); printProgress.setText(format.format(msgParams)); } /** @return preferred size which is greater than computed by awt */ public Dimension getPreferredSize() { return new Dimension(200, 60); } } /** Font for CancellationDialog */ private static Font fontInstance; /** @return cached font instance */ static Font getFontInstance() { if (fontInstance == null) { fontInstance = new java.awt.Font("Dialog", java.awt.Font.PLAIN, 14); // NOI18N } return fontInstance; } } /* * Log * 14 src-jtulach1.13 1/13/00 Ian Formanek NOI18N * 13 src-jtulach1.12 10/26/99 Ales Novak #3732 * 12 src-jtulach1.11 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun * Microsystems Copyright in File Comment * 11 src-jtulach1.10 8/17/99 Ian Formanek Generated serial version * UID * 10 src-jtulach1.9 7/7/99 Jesse Glick Context help. * 9 src-jtulach1.8 6/8/99 Ian Formanek ---- Package Change To * org.openide ---- * 8 src-jtulach1.7 6/8/99 Ales Novak IllegalArgumentException * from Bidi * 7 src-jtulach1.6 6/4/99 Ales Novak # 1970 * 6 src-jtulach1.5 5/12/99 Ales Novak cancellation featurefix * 5 src-jtulach1.4 5/11/99 Ales Novak filenames used + footer * bug + line ascent correction + cancel dialog * 4 src-jtulach1.3 4/30/99 Ales Novak options take part in * printing * 3 src-jtulach1.2 4/23/99 Ales Novak color added * 2 src-jtulach1.1 4/21/99 Ales Novak first version of * printing * 1 src-jtulach1.0 2/19/99 Ales Novak * $ */